﻿using Devil;
using GameToolkit;
using UnityEngine;

namespace DynamicBone
{
    public abstract class DynamicBoneColliderBase : MonoBehaviour, INative<DynamicCollider>, ICullingAction, IVolumetric, IThreadSafty<IVolumetric>
    {
        // 为了减少空间划分更新频率，定义碰撞区域单位大小，只有在单位坐标改变时更新空间
        //readonly static float SNAP_UNIT = 1f;
        readonly static float INV_SNAP_UNIT = 1f;

        public enum Direction
        {
            X, Y, Z
        }

        public int InstanceID { get { return GetInstanceID(); } }

#if UNITY_5_3_OR_NEWER
        [Tooltip("The axis of the capsule's height.")]
#endif
        public Direction m_Direction = Direction.Y;

#if UNITY_5_3_OR_NEWER
        [Tooltip("The center of the sphere or capsule, in the object's local space.")]
#endif
        public Vector3 m_Center = Vector3.zero;

        public enum Bound
        {
            Outside,
            Inside
        }

#if UNITY_5_3_OR_NEWER
        [Tooltip("Constrain bones to outside bound or inside bound.")]
#endif
        public Bound m_Bound = Bound.Outside;

        Bounds mGlobalBounds;
        DynamicCollider mData;
        public Matrix4x4 localToWorldMatrix { get { return Matrix4x4.identity; } }
        public Matrix4x4 worldToLocalMatrix { get { return Matrix4x4.identity; } }
        public Bounds localBounds { get { return mGlobalBounds; } }
        IVolumetric IThreadSafty<IVolumetric>.GetSaftyInstance() { return this; }

        Bounds GetGlobalBounds()
        {
            var cent = transform.localToWorldMatrix.MultiplyPoint(m_Center);
            cent.x = Mathf.Round(cent.x * INV_SNAP_UNIT) * INV_SNAP_UNIT;
            cent.y = Mathf.Round(cent.y * INV_SNAP_UNIT) * INV_SNAP_UNIT;
            cent.z = Mathf.Round(cent.z * INV_SNAP_UNIT) * INV_SNAP_UNIT;
            InitNativeData(ref mData);
            var size = MathExt.Max(mData.size.x, mData.size.y, mData.size.z);
            return new Bounds(cent, 1.5f * size * Vector3.one);
        }

        public virtual bool Collide(ref Vector3 particlePosition, float particleRadius)
        {
            return false;
        }

        protected abstract void InitNativeData(ref DynamicCollider col);

        public DynamicCollider GetNativeData()
        {
            InitNativeData(ref mData);
            mData.id = InstanceID;
            return mData;
        }

        bool mVisible;
        NativeInstance<DynamicCollider> mNativeInst;
        public bool HasNativeInstance
        {
            get
            {
                return mNativeInst != null && mNativeInst.IsAllocated;
            }
        }
        public NativeInstance<DynamicCollider> NativeInstance { get { return mNativeInst; } }

        protected virtual void OnEnable()
        {
            if (mVisible && mNativeInst == null)
            {
                mNativeInst = DynamicBoneJobs.AddCollider(this);
            }
        }

        protected virtual void OnDisable()
        {
            if (mNativeInst != null)
            {
                DynamicBoneJobs.RemoveCollider(mNativeInst);
                mNativeInst = null;
            }
        }

        public void OnCulling(CullingCamera cam, bool visible, int distanceLv)
        {
            var update = visible && distanceLv < 3 || distanceLv < 1;
            if (update != mVisible && isActiveAndEnabled)
            {
                if (update && mNativeInst == null)
                {
                    mNativeInst = DynamicBoneJobs.AddCollider(this);
                }
                else if (!update && mNativeInst != null)
                {
                    DynamicBoneJobs.RemoveCollider(mNativeInst);
                    mNativeInst = null;
                }
            }
            mVisible = update;
        }

#if UNITY_EDITOR
        protected virtual void OnValidate()
        {
            if (mNativeInst != null)
                MainThread.RunOnMainThread(mNativeInst.UpdateNativeData);
            mGlobalBounds = GetGlobalBounds();
            //mNativeInst.UpdateNativeData();
        }

        protected virtual void OnDrawGizmosSelected()
        {
            var bounds = GetGlobalBounds();
            Gizmos.matrix = Matrix4x4.identity;
            Gizmos.color = Color.gray;
            Gizmos.DrawWireCube(bounds.center, bounds.size);
        }
#endif
    }
}